﻿using System;
using System.Collections.Generic;
using System.ServiceModel;
using NativeWifi;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading;
using System.Collections;
using System.Diagnostics;
using System.Text;
using System.ComponentModel;

namespace Client {

	[CallbackBehavior(UseSynchronizationContext = true, ConcurrencyMode = ConcurrencyMode.Reentrant, IncludeExceptionDetailInFaults = true)]
	class ServiceModelServerCallback : IServiceModelServerCallback {
		protected Dictionary<Guid, TestResult> testResults;
		protected Process customTestServer;

		public ServiceModelServerCallback() {
			testResults = new Dictionary<Guid, TestResult>();
		}

		#region IServiceModelServerCallback Members

		public Boolean ResponseTestConnection() {
			Logger.Debug("Sending response to server for TestConnection method from '" + this.GetType().Name + "' class");
			return true;
		}
		/// <summary>
		/// Metoda używana przez serwer do pobierania logów z klienta
		/// </summary>
		/// <returns>Log klienta</returns>
		public String[] GetLogs() {
			//Logger.Debug("Callback GetLogs() fired");
			return Logger.GetLogs();
		}

		/// <summary>
		/// Metoda do odczytywania z klienta linijek logu, poczynając od podanego indeksu
		/// </summary>
		/// <param name="index">Indeks linijki</param>
		/// <returns>Zwraca string z kolejnymi linijkami loga</returns>
		public String[] GetLogsLastLines(int index) {
			//Logger.Debug("Callback GetLogsLast(" + index.ToString() + ") fired");
			return Logger.GetLogs(index);
		}

		/// <summary>
		/// Metoda zwracająca status połączenia
		/// </summary>
		/// <returns>WifiConnectionState</returns>
		public WifiConnectionState WifiGetConnectionState() {
			Logger.Info("Getting connection state..");
			return Wifi.Instance.ConnectionState;
		}

		/// <summary>
		/// Metoda wykonywana na kliencie w celu podłączenia do sieci bezprzewodowej.
		/// Na czas działania programu tworzony jest profil wifiperf w sieciach bezprzewodowych.
		/// Po zakończeniu połączenia jest on usuwany.
		/// </summary>
		/// <param name="ssid">SSID Sieci</param>
		/// <param name="securityType">Open WEP WPA WPA2</param>
		/// <param name="encryptionType">TKIP EAS</param>
		/// <param name="securityKey">Hasło</param>
		/// <returns>Zwraca true jeżeli udało się połączyć, false w przeciwnym wypadku</returns>
		public bool WifiConnect(string ssid, WifiSecurityType securityType, WifiEncryptionType encryptionType, string securityKey) {
			Wifi.Instance.SecurityKey = securityKey;
			Wifi.Instance.SecurityType = securityType;
			Wifi.Instance.EncryptionType = encryptionType;
			Wifi.Instance.SSID = ssid;
			return Wifi.Instance.Connect();
		}

		/// <summary>
		/// Metoda rozłączająca klienta z sieci bezprzewodowej.
		/// Usuwa profil sieci bezprzewodowej.
		/// </summary>
		/// <returns>true</returns>
		public bool WifiDisconnect() {
			Wifi.Instance.DeleteProfile();
			Wifi.Instance.Disconnect();
			return true;
		}

		/// <summary>
		/// Metoda wykonywana na kliencie w celu sprawdzenia opóźnień czasowych
		/// do zadanego celu.
		/// </summary>
		/// <param name="host">Host który ma być pingowany</param>
		/// <param name="count">Ilość pingów</param>
		/// <param name="interval">Interwał pomiędzy pingami</param>
		/// <param name="timeout">Timeout</param>
		/// <param name="async">1 nie czekaj na odpowiedzi - wysyłaj kolejne,
		/// 0 - czekaj na odpowiedz przed wysłaniem kolejnego</param>
		/// <returns></returns>
		public Guid TestPing(String testName, String host, int count, int interval, int timeout, bool async) {
			Guid testId = Guid.NewGuid();
			Test test = new Test();
			TestResult result = new TestResult();
			result.StartTime = DateTime.Now;
			result.Node = ServiceClient.Instance.Name;
			result.Id = testId;
			result.Results = new String[0];
			if (null == testName || testName.Trim().Length <= 0) {
				testName = "ping/Unknown";
			}
			result.Name = testName;
			result.ResultsHeader = "Status, RoundtripTime";
			result.Description = String.Format("Pinging host {0}, {1} times with {2}ms interval and {3}s timeout", host, count, interval, timeout);

			List<String> pingResults = new List<String>();
			int pings = count;

			if (count <= 0) {
				count = 1;
			}

			if (interval < 1) {
				interval = 1;
			}

			if (timeout < 1) {
				timeout = 1;
			}

			test.DoTest += (o, e) => {
				Logger.Test(result.Description);

				for (int i = 0; i < count; ++i) {
					//Logger.Debug("Ping request!");

					Ping ping = new Ping();

					ping.PingCompleted += (obj, ea) => {
						// If the operation was canceled, display a message to the user.
						if (ea.Cancelled) {
							Logger.Info("Ping canceled.");
						}

						// If an error occurred, display the exception to the user.
						if (ea.Error != null) {
							Logger.Warning(String.Format("Ping Exception ({0}): {1}", ea.Error.GetType().Name, ea.Error.Message));
						} else {
							pingResults.Add(String.Format("\"{0}\",{1}", ea.Reply.Status, ea.Reply.RoundtripTime.ToString()));
						}

						if (0 == Interlocked.Decrement(ref pings)) {
							Logger.Debug("Pinging done...");
							result.EndTime = DateTime.Now;
							result.Results = pingResults.ToArray();
							testResults.Add(testId, result);
							ServiceClient.Instance.GetService().TestResultReady(testId);
						}
					};

					try {
						if (async) {
							AutoResetEvent waiter = new AutoResetEvent(false);
							ping.SendAsync(host, timeout * 1000, waiter);
						} else {
							PingReply reply = ping.Send(host, timeout * 1000);
							pingResults.Add(String.Format("\"{0}\",{1}", reply.Status, reply.RoundtripTime.ToString()));
						}
					} catch (ArgumentNullException) {
						Logger.Warning("Host name to ping is empty");
					} catch (SocketException) {
						Logger.Warning(String.Format("String '{0}' is no valid host or ip address", host));
					} catch (PingException ex) {
						Logger.Warning(String.Format("An exception was thrown while sending or receiving the ICMP messages ({0})", ex.InnerException.Message));
					} catch (InvalidOperationException ex) {
						Logger.Warning(ex.Message);
					} catch (NotSupportedException ex) {
						Logger.Warning(ex.Message);
					}
					Thread.Sleep(interval);
				}

			};

			test.TestCompleted += (o, e) => {
				if (!async) {
					Logger.Info("Pinging done...");
					result.EndTime = DateTime.Now;
					result.Results = pingResults.ToArray();
					testResults.Add(testId, result);
					ServiceClient.Instance.GetService().TestResultReady(testId);
				}
			};

			// test.BeforeTest +=

			test.RunAsync();

			return testId;
		}

		/// <summary>
		/// Metoda zwracająca wynik testu o podanym identyfikatorze
		/// </summary>
		/// <param name="guid">GUID testu</param>
		/// <returns>false w przypadku błędnego GUID, Wyniki testu w przypadku dopasowania</returns>
		public TestResult GetTestResult(Guid guid) {
			if (testResults.ContainsKey(guid)) {
				TestResult result = testResults[guid];
				testResults.Remove(guid);
				return result;
			} else {
				return null;
			}
		}
		/// <summary>
		/// Zwraca wszystkie wyniki testów
		/// </summary>
		/// <returns>Słownik z identyfikatorem testu jako klucz i wynikiem jako zawartość</returns>
		public Dictionary<Guid, TestResult> GetTestResults() {
			return testResults;
		}

		/// <summary>
		/// Metoda używana w celu sprawdzenia czy węzeł posiada 
		/// wynik testu o żadanym GUID.
		/// </summary>
		/// <param name="guid">GUID który sprawdzamy</param>
		/// <returns>0 - gdy nie posiada, 1 - gdy posiada</returns>
		public Boolean ContainsResult(Guid guid) {
			return testResults.ContainsKey(guid);
		}

		/// <summary>
		/// Metoda służaca do pobierania listy z kluczami testów
		/// </summary>
		/// <returns>Zwraca liste guid testów</returns>
		public Guid[] GetTestResultList() {
			Guid[] guids = new Guid[testResults.Count];
			testResults.Keys.CopyTo(guids, 0);

			return guids;
		}


		public void StartCustomTestServer(String command, int timeout) {
			if (null != customTestServer) {
				StopCustomTestServer();
			}
			if (timeout <= 0) {
				timeout = Int32.MaxValue;
			}

			BackgroundWorker backgroundWorker = new BackgroundWorker();

			backgroundWorker.DoWork += (o, e) => {
				customTestServer = executeCommand(command);
				customTestServer.WaitForExit(timeout);
			};

			backgroundWorker.RunWorkerCompleted += (o, e) => {
				String error = customTestServer.StandardError.ReadToEnd();
				error.Trim(new char[] { '\n', '\r', '\t' });
				StopCustomTestServer();
				if (error.Length > 0) {
					Logger.Warning("Execute Server Command returns Error: " + error);
				}
			};

			Logger.Debug(String.Format("Starting CustomTestServer \"{0}\"...", command));
			backgroundWorker.RunWorkerAsync();
		}

		public void StopCustomTestServer() {
			if (null != customTestServer) {
				Logger.Debug("Stopping CustomTestServer...");
				customTestServer.Close();
				customTestServer = null;
			}
		}

		public Guid TestCustomTest(String testName, String command, int timeout) {
			Guid testId = Guid.NewGuid();
			Test test = new Test();
			TestResult result = new TestResult();
			Process process;

			result.Node = ServiceClient.Instance.Name;
			result.Id = testId;
			result.Name = testName;
			result.Description = command;
			result.ResultsHeader = "; " + command;
			result.Results = new String[0];

			if (timeout <= 0) {
				timeout = Int32.MaxValue;
			}

			Logger.Test(String.Format("Starting {0} Test (\"{1}\")...", testName, command));

			test.DoTest += (o, e) => {
				process = executeCommand(command);
				process.WaitForExit(timeout);
				int exitCode = process.ExitCode;

				if (exitCode == 0) {
					e.Result = process.StandardOutput.ReadToEnd();
				}

				String error = process.StandardError.ReadToEnd();
				error.Trim(new char[] { '\n', '\r', '\t' });

				process.Close();

				if (error.Length > 0) {
					Logger.Warning("Execute Command returns Error: " + error);
				}
			};

			test.TestCompleted += (o, e) => {
				result.EndTime = DateTime.Now;

				if (null == e.Error && e.Result != null && ((String) e.Result).Length > 0) {
					result.Results = ((String) e.Result).Split(new char[] { '\n' });
				} else {
					Logger.Warning(String.Format("Test name {0}, which executing command \"{1}\" failure!", testName, command));
				}

				Logger.Info(String.Format("Test {0} has finished", testName));

				testResults.Add(testId, result);
				ServiceClient.Instance.GetService().TestResultReady(testId);
			};

			result.StartTime = DateTime.Now;

			test.Run();

			return testId;
		}

		public void Shutdown() {
			Environment.Exit(0);
		}

		protected static Process executeCommand(string Command) {
			ProcessStartInfo processInfo;
			Process process;

			processInfo = new ProcessStartInfo("cmd.exe", "/C " + Command);
			processInfo.WorkingDirectory = "C:\\";
			processInfo.CreateNoWindow = true;
			processInfo.UseShellExecute = false;
			processInfo.RedirectStandardError = true;
			processInfo.RedirectStandardOutput = true;
			processInfo.StandardErrorEncoding = Encoding.UTF8;
			processInfo.StandardOutputEncoding = Encoding.UTF8;

			process = Process.Start(processInfo);

			return process;
		}

		#endregion
	}
}
